﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
using System.Windows.Interop;
using Relay.UI.Controls.Win32;
using System.Threading;

namespace Relay.UI.Controls.Docking {
    internal class FloatingWindow : Window, IDockHierarchyObjectHost {
        #region Fields
        private SplitPanel _panel = null;
        private DockManager _owner = null;
        private bool _endSession = false;
        #endregion

        #region Constructor
        static FloatingWindow() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FloatingWindow),
                new FrameworkPropertyMetadata(typeof(FloatingWindow)));
            WindowStyleProperty.OverrideMetadata(typeof(FloatingWindow),
                new FrameworkPropertyMetadata(WindowStyle.ToolWindow));
            ShowInTaskbarProperty.OverrideMetadata(typeof(FloatingWindow),
                new FrameworkPropertyMetadata(false));
        }

        public FloatingWindow(DockWindowGroup group, DockManager owner) {
            Owner = Window.GetWindow(owner);

            // There is an obscure bug in WPF which causes sometimes subtle focusing issues when a
            // currently focused control is reparented. In this case, the content of the currently-being-reparented
            // DockWindowGroup is usually focused so it triggers the bug, a fix for which is presented in the following
            // two lines. It took four days to figure out how to fix this.
            FocusManager.SetFocusedElement(Owner, null);
            Keyboard.Focus(null);

            IDockHierarchyObjectHost parent = group.Parent as IDockHierarchyObjectHost;
            if (parent != null) {
                parent.RemoveChild(group);
            }

            _panel = new SplitPanel();
            _owner = owner;

            _panel.Children.Add(group);
            _panel.Floating = true;

            Content = _panel;

            owner.RegisterFloatingWindow(this);

            Width = group.FloatedSize.Width + (SystemParameters.ResizeFrameVerticalBorderWidth * 2);
            Height = group.FloatedSize.Height + (SystemParameters.ResizeFrameHorizontalBorderHeight * 2 + SystemParameters.SmallCaptionHeight);
        }

        private FloatingWindow(SplitPanel panel, DockManager owner, Rect rect) {
            Owner = Window.GetWindow(owner);

            FocusManager.SetFocusedElement(Owner, null);
            Keyboard.Focus(null);

            IDockHierarchyObjectHost parent = panel.Parent as IDockHierarchyObjectHost;
            if (parent != null) {
                parent.RemoveChild(panel);
            }

            _panel = panel;
            _owner = owner;

            _panel.Floating = true;

            Left = rect.X;
            Top = rect.Y;
            Width = rect.Width;
            Height = rect.Height;

            Content = _panel;
        }
        #endregion

        #region Properties
        public SplitPanel Panel {
            get { return _panel; }
        }

        public DockManager Manager {
            get { return _owner; }
        }
        #endregion

        #region Private Methods
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            switch (msg) {
                case Interop.WM_NCLBUTTONDOWN:
                    if (WindowState == WindowState.Normal && wParam.ToInt32() == Interop.HTCAPTION) {
                        _owner.DockService.Start(this);
                    }

                    break;
                case Interop.WM_NCLBUTTONDBLCLK:
                    handled = true;
                    break;
                case Interop.WM_QUERYENDSESSION:
                    _endSession = true;
                    break;
            }

            return IntPtr.Zero;
        }
        #endregion

        #region Overrides
        public new void Show() {
            base.Show();

            // If I don't do this, something bugs out and causes the focus to remain in limbo until I click somewhere,
            // eating the mouse click, and thus requiring two clicks to do anything after opening a FloatingWindow.
            // It's caused by the _contentPresenter.MoveFocus() call in the DockWindowGroup's TitleBarMouseDown
            // method, but I don't see how to fix it from there.
            MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
        }

        protected override void OnSourceInitialized(EventArgs e) {
            HwndSource wnd = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
            wnd.AddHook(new HwndSourceHook(WndProc));

            base.OnSourceInitialized(e);
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {
            base.OnRenderSizeChanged(sizeInfo);

            foreach (FrameworkElement element in _panel.Children) {
                if (element is DockWindowGroup) {
                    DockWindowGroup grp = element as DockWindowGroup;
                    grp.FloatedSize = grp.RenderSize;
                }
            }
        }

        protected override Size MeasureOverride(Size availableSize) {
            Visibility = _panel.Visibility;

            return base.MeasureOverride(availableSize);
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e) {
            base.OnClosing(e);

            _owner.UnregisterFloatingWindow(this);

            if (!_endSession && Content == _panel && _panel.Children.Count > 0) {
                Content = null;

                // Create a new, invisible FloatingWindow hosting the panel so that it can be shown again implicitly.
                FloatingWindow newWindow = new FloatingWindow(_panel, _owner, new Rect(Left, Top, Width, Height));
            }
        }        
        #endregion

        #region IDockHierarchyObject Members
        DockManager IDockHierarchyObject.GetDockManager() {
            return _owner;
        }

        IDropTarget[] IDockHierarchyObject.FindDropTargets() {
            return ((IDockHierarchyObject)_panel).FindDropTargets();
        }

        void IDockHierarchyObjectHost.MakeVisible(bool show) {
            if (show) Show(); else Hide();
        }

        void IDockHierarchyObjectHost.MakeDirty() {
            // Nothing needed
        }

        void IDockHierarchyObjectHost.RemoveChild(FrameworkElement element) {
            if (element != _panel) throw new ArgumentOutOfRangeException("element");
            Content = null;
            Close();
        }
        #endregion

    }
}
